import {NextFunction, RequestHandler, Request, Response} from 'express'
import bodyParser from 'body-parser';
import multer from 'multer';
import {pathToRegexp} from 'path-to-regexp';

import {join} from 'path'
import {existsSync} from 'fs';

import winPath from './winPath';

import glob from 'glob'


export interface IApi {
    //cwd The current working directory in which to search. Defaults to process.cwd().
    cwd: string,
    babelRegister: any
}

interface IOpts {
    api: IApi;
}

interface IGetMockPaths extends Required<Pick<IApi, 'cwd'>> {
    ignore?: string[],
    registerBabel?: (paths: string[]) => any
}


export interface IMockDataItem {
    method: string;
    path: string;
    re: RegExp;
    keys: any[];
    handler: RequestHandler;
}

export interface IGetMockDataResult {
    mockData: IMockDataItem[];
    mockPaths: string[];
    mockWatcherPaths: string[];
}

export const getMockData: (opts: IGetMockPaths) => IGetMockDataResult = ({
                                                                             cwd,
                                                                             ignore = [],
                                                                             registerBabel = () => {
                                                                             }
                                                                         }) => {
    const mockPaths = [
        ...(glob.sync('src/mock/**/*.[jt]s', {
            cwd,
            ignore,
        }) || []),
    ]
        .map((path) => {
            path = path.replace(/^src/, 'dist')
            path = path.replace(/(\.ts)$/, '.js')

            return join(cwd, path)
        })
        .filter(path => path && existsSync(path))
        .map(path => winPath(path))
    console.log(`load mock data including files ${JSON.stringify(mockPaths)}`);
    // register babel
    registerBabel(mockPaths);
    // get mock data

    const mockData: IMockDataItem[] = normalizeConfig(getMockConfig(mockPaths))

    const mockWatcherPaths = [...(mockPaths || []), join(cwd, 'mock')]
        .filter((path) => path && existsSync(path))
        .map((path) => winPath(path));
    return {
        mockData,
        mockPaths,
        mockWatcherPaths
    }
}

export const getMockConfig = (files: string[]): object => {

    return files.reduce((memo, mockFile) => {


        try {

            // 调用require读取文件内容
            const m = require(mockFile)

            memo = {
                ...memo,
                ...(m.default || m.res || m),
            };

            return memo;
        } catch (e) {
            throw new Error(e.stack);
        }
    }, {});
};

export const normalizeConfig = (config: any) => {
    return Object.keys(config).reduce((memo: any, key) => {
        const handler = config[key];
        const type = typeof handler;

        const {method, path} = parseKey(key);
        const keys: any[] = [];
        const re = pathToRegexp(path, keys);

        memo.push({
            method,
            path,
            re,
            keys,
            handler: createHandler(method, path, handler),
        });
        // console.log(memo)
        return memo;
    }, []);
};

function parseKey(key: string) {
    let method = 'get';
    let path = key;
    if (/\s+/.test(key)) {
        const splited = key.split(/\s+/);
        method = splited[0].toLowerCase();
        path = splited[1]; // eslint-disable-line
    }

    return {
        method,
        path,
    };
}

const BODY_PARSED_METHODS = ['post', 'put', 'patch', 'delete'];

function createHandler(method: any, path: any, handler: any): RequestHandler {
    return function (req: Request, res: Response, next: NextFunction) {
        if (BODY_PARSED_METHODS.includes(method)) {
            bodyParser.json({limit: '5mb', strict: false})(req, res, () => {
                bodyParser.urlencoded({limit: '5mb', extended: true})(
                    req,
                    res,
                    () => {
                        sendData();
                    },
                );
            });
        } else {
            sendData();
        }

        function sendData() {
            // 是函数走multer
            if (typeof handler === 'function') {

                // Multer 是一个 node.js 中间件，
                // .any() Accepts all files that comes over the wire.
                multer().any()(req, res, () => {
                    handler(req, res, next);
                });
            } else {
                // 不是函数直接返回
                res.json(handler);
            }
        }
    };
}


